import BaseChart from '../../BaseChart.js'

/**
 * 力导向图
 */
export default class ForceRelationship extends BaseChart {
  constructor(id, data) {
    super(id, data);
    //点集
    this.nodes = this.data['nodes'];
    // 边集
    this.links = this.data['links'];
    // 边
    this.linksGroup = null;
    this.linksGroupElement = null;
    // 点
    this.nodesGroup = null;
    this.nodesGroupElement = null;
    // 文字
    this.textsGroup = null;
    this.textsGroupElement = null;
    // 边上的文字
    this.linksText = null;
    // 箭头
    this.linksMarkerElement = null;
    // 力导向图
    this.forceSimulation = null;

    // 比例尺的属性
    // 颜色比例尺
    this.colorScale = null;
    // 半径比例尺
    this.r = null;

    this.colorKey = 'name'
    this.sizeKey = 'size';
    this.nameKey = 'name';
    this.textColor = 'black';
    this.lineColor = '#212121';
    this.linksMarker = true;
    this.linksScale = null;
    this.tempIndex = null;

    this.linkedByIndex = {};
  }

  setNameKey(k) {
    this.nameKey = k;
    return this;
  }

  setColorKey(k) {
    this.colorKey = k;
    return this;
  }

  setSizeKey(k) {
    this.sizeKey = k;
    return this;
  }

  setTextColor(color) {
    this.textColor = color;
    return this;
  }

  setLineColor(color) {
    this.lineColor = color;
    return this;
  }
  // 是否要加箭头
  setLinksMarker(bool) {
    this.linksMarker = bool;
    return this;
  }

  /**
   * @param {回调事件} callback 
   */
  onClickNode(callback){
    this.onClickNodeCallback = callback;
    return this;
  }

  onClickNodeHandler(event,data){
    let targets = [];
    let sources = [];
    let name = data[this.nameKey];
    this.links.forEach(d=>{
      if(d.source[this.nameKey]===name){
        targets.push(d);
      }
      if(d.target[this.nameKey]===name){
        sources.push(d);
      }
    })
    this.onClickNodeCallback(event,data,targets,sources);
  }
  

  init() {
    //初始化svg
    this.initSvg();
    //初始化主题内容部分
    this.initMainGroup();
    //初始化伸缩功能;
    this.initZoom();
    // 初始化texts\links\nodes容器
    this.initGroup();
    // 初始化箭头marker
    this.initMarker();
    return this;
  }

  initGroup() {
    this.textsGroup = this.mg.append('g').attr("id","forceTextsGroup")
    this.linksGroup = this.mg.append('g').attr("id","forceLinksGroup")
    this.nodesGroup = this.mg.append('g').attr("id","forceNodesGroup")
  }

  initZoom() {
    this.svg.call(d3.zoom()
      .on('zoom', this.zoomed)
    )
  }

  initMarker() {
    if (this.linksMarker) {
      this.linksMarkerElement = this.linksGroup.append("marker")
        .attr("id", "resolved")
        .attr("markerUnits", "strokeWidth") //设置为strokeWidth箭头会随着线的粗细发生变化
        .attr("markerUnits", "userSpaceOnUse")
        .attr("viewBox", "0 -5 10 10") //坐标系的区域
        .attr("refX", 40) //箭头坐标
        .attr("refY", 0)
        .attr("markerWidth", 6) //标识的大小
        .attr("markerHeight", 6)
        .attr("orient", "auto") //绘制方向，可设定为：auto（自动确认方向）和 角度值
        .attr("stroke-width", 2) //箭头宽度
        .append("path")
        .attr("d", "M0,-5L10,0L0,5") //箭头的路径
        .attr('fill', 'rgba(50,50,50,0.8)'); //箭头颜色
    }
  }
  // 创建一个力导向图
  render(data) {
    if (data) {
      this.data = data;
      // 点集
      this.nodes = data['nodes'];
      // 边集
      this.links = data['links'];
    }
    // 更新比例尺
    this.renderScale();
    // 生成一个力导向图模型算法
    this.renderForce();
    // 绘制边上文字
    this.renderTexts();
    // 描绘结点
    this.renderNodes();
    // 描绘边
    this.renderLinks();

    return this;
  }

  // 颜色比例尺
  renderScale() {
    // 颜色比例尺
    // 取出所有的名字
    let colorGroup = [];
    this.nodes.forEach(d => {
      colorGroup.push(d[this.colorKey])
    })
    this.colorScale = d3.scaleOrdinal()
      .domain(colorGroup)
      .range(d3.schemeSet3)
      .unknown("red")
    //半径比例尺
    let nodeSize = [];
    this.nodes.forEach(d => {
      nodeSize.push(d[this.sizeKey])
    })
    this.r = d3.scaleLinear()
      .domain([0, d3.max(nodeSize)])
      .range([.5, 15]);

    // 边的比例尺
    // 取出所有的边的value
    let linkWidth = [];
    this.links.forEach(d => {
      linkWidth.push(d["value"])
    })
    this.linksScale = d3.scaleLinear()
      .domain([0, d3.max(linkWidth)])
      .range([.5, 4])

  }
  // 生成一个力导向图
  renderForce() {
    this.forceSimulation = d3.forceSimulation(this.nodes)
      .force("link", d3.forceLink(this.links).id(d => d[this.nameKey]))
      .force("charge", d3.forceManyBody())
      .force("center", d3.forceCenter(this.innerWidth / 2, this.innerHeight / 2))
    // .force("collide", d3.forceCollide())
    // .force("x", d3.forceX(0).strength(0.1))
    // .force("y", d3.forceY(0).strength(0.1))
    this.forceSimulation.on('tick', () => {
      this.linksGroupElement.attr("x1", function (d) {
          return d.source.x;
        })
        .attr("y1", (d)=> d.source.y)
        .attr("x2", (d) =>d.target.x)
        .attr("y2", function (d) {
          return d.target.y;
        });
      this.nodesGroupElement.attr("transform", function (d) {
        return `translate(${d.x},${d.y})`
      });
      this.textsGroupElement.attr("transform", function (d) {
        return `translate(${d.x + 15},${d.y})`
      });
      if (this.linksMarker) {
        this.linksGroupElement.attr("d", function (d) {
          return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y
        })
      }
    })
  }

  // 描绘结点+结点上面的文字+箭头的透明度
  renderNodes() {
    let _ = this;
    this.nodesGroupElement = this.nodesGroup
      .selectAll('circle')
      .data(this.nodes)
      .join('circle')
      .style('cursor', 'pointer')
      .attr("stroke", "#fff")
      .attr("stroke-width", 1.5)
      .attr("r", d => this.r(d[this.sizeKey]))
      .attr("fill", (d) => this.colorScale(d[this.colorKey]))
      .attr("transform", d => `translate(${d.x},${d.y})`)
      .call(d3.drag()
        .on("start", this.started)
        .on("drag", this.dragged)
        .on("end", this.ended)
      )
      .on('mouseover', fade(0.1))
      .on('mouseout', fade(0.8))
      .on("click",(event,d)=>{
        strong(event,d);
        this.onClickNodeHandler(event,d)
      })
      
    this.links.forEach(d => {
      this.linkedByIndex[`${d.source.index},${d.target.index}`] = 1;
    });

    const strong =(event, d) =>{
        _.nodesGroupElement.attr("r", (o) => _.isConnected(d, o) ? _.r(o.size + 20) : _.r(o.size))
        _.linksGroupElement.style('stroke-width', (o) => (o.source === d || o.target === d) ? _.linksScale(o.value) * 2 : _.linksScale(o.value))
        if(this.linksMarker)
          _.linksMarkerElement.style('opacity', 0.8);
    };

    function fade(opacity) {
      return (event, d) => {
        _.nodesGroupElement.style('opacity', (o) =>_.isConnected(d, o) ? 1 : opacity);
        _.textsGroupElement.style('visibility', (o) => _.isConnected(d, o) ? "visible" : "hidden");
        _.linksGroupElement.style('stroke-opacity', (o) => (o.source === d || o.target === d ? 0.8 : opacity));
        _.linksGroupElement.attr('marker-end', (o) => (o.source === d || o.target === d ? 'url(#resolved)' : null))
        if (opacity === 0.8) {
          _.nodesGroupElement.style('opacity', 1)
          _.textsGroupElement.style('visibility', 'hidden')
          _.linksGroupElement.style('stroke-opacity', 1)
          _.linksGroupElement.attr('marker-end', null)
        }
      };
    }
  }

  isConnected =(a, b)=> {
    return this.linkedByIndex[`${a.index},${b.index}`] || this.linkedByIndex[`${b.index},${a.index}`] || a.index === b.index;
  }

  // 描绘边
  renderLinks() {
    this.linksGroupElement = this.linksGroup
      .selectAll('line')
      .data(this.links)
      .join('line')
      .attr("stroke", this.lineColor)
      .attr("stroke-opacity", 0.8)
      .attr("stroke-width", d => this.linksScale(d.value))
      .attr("d", (d) => 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y)
  }

  // 描述结点上的字
  renderTexts() {
    this.textsGroupElement = this.textsGroup
      .selectAll('text')
      .data(this.nodes)
      .join('text')
      .attr("transform", d => `translate(${d.x},${d.y})`)
      .text(d => d[this.nameKey])
      .attr('fill', this.textColor)
      .style("visibility", "hidden")
      .style("font-size", d => this.r(d.size))
  }


  // 拖拽
  started = (event, d) => {
    if (!event.active) {
      this.forceSimulation.alphaTarget(0.3).restart();
    }
    event.subject.fx = d.x;
    event.subject.fy = d.y;
  }
  dragged = (event, d) => {
    event.subject.fx = event.x;
    event.subject.fy = event.y;
  }
  ended = (event, d) => {
    if (!event.active) {
      this.forceSimulation.alphaTarget(0);
    }
    event.subject.fx = null;
    event.subject.fy = null;
  }
  // 伸缩函数
  zoomed = (event) => {
    this.mg.attr('transform', `translate(${event.transform.x},${event.transform.y}) scale(${event.transform.k})`)
  }
}